﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using ProjectRoughWork.Engine.SoundManager;
namespace ProjectRoughWork
{
    class Level
    {

        /// <summary>
        /// Default Constructor. Initializes level size and level tile array
        /// </summary>

        public Level()
        {
            levelSize = 10;

            level = new LevelTile[levelSize, levelSize];

            initSquadPos = new Vector3[2, 3];

            //Initialize Player Squad positions

            initSquadPos[0, 0] = new Vector3(-100, 0, 200);
            initSquadPos[0, 1] = new Vector3(0, 0, 200);
            initSquadPos[0, 2] = new Vector3(100, 0, 200);

            //Initialize Enemy Squad positions

            initSquadPos[1, 0] = new Vector3(-100, 0, -300);
            initSquadPos[1, 1] = new Vector3(0, 0, -300);
            initSquadPos[1, 2] = new Vector3(150, 0, -300);

            Init();
            SetUpCover();
            SetUpNavMesh();
        }

        /// <summary>
        /// Initializes all the level tiles and its necessary variables as well as calcualtes view and projection matrix 
        /// </summary>

        public void Init()
        {
            cameraPos = new Vector3(0, 600, 450);
            
            // Our listener is our camera, make sure our sound manager is created and set up for that
            SoundManager.GetInstance().setListenerPosition(cameraPos);

            cameraTarget = Vector3.Zero;
            lookUpVector = new Vector3(0, 1, 0);

            view = Matrix.CreateLookAt(cameraPos, cameraTarget, lookUpVector);
            projection = Matrix.CreatePerspectiveFieldOfView(
            MathHelper.ToRadians(50),  // 45 degree angle
            (float)Game1.GetGraphicsDevice().Viewport.Width / (float)Game1.GetGraphicsDevice().Viewport.Height,
            1.0f, 2000.0f);

            //Set Up Custom Effect's view and projection

            Game1.GetCustomEffect().Parameters["View"].SetValue(view);
            Game1.GetCustomEffect().Parameters["Projection"].SetValue(projection);

            //Load textures for drawing the tiles

            Texture2D grass = Game1.GetContentManager().Load<Texture2D>("grassTexture");
            Texture2D water = Game1.GetContentManager().Load<Texture2D>("waterTexture");
            Texture2D initTexture = grass;

            //Initialize tile size

            tileSize = 80f;

            //Initialize first tile position

            position = new Vector3(-(levelSize * tileSize) / 2, 0, -(levelSize * tileSize) / 2);
            Vector3 initTilePos = new Vector3(position.X, position.Y, position.Z);

            //Loop through level tiles, initializes them with appropriate valies

            for (int outerLoop = 0; outerLoop < levelSize; outerLoop++)
            {
                for (int innerLoop = 0; innerLoop < levelSize; innerLoop++)
                {
                    if (tileTypeLayout[outerLoop, innerLoop] == 0)
                    {
                        initTexture = grass;
                    }
                    else if (tileTypeLayout[outerLoop, innerLoop] == 1)
                    {
                        initTexture = water;
                    }

                    level[outerLoop, innerLoop] = new LevelTile(initTilePos, tileSize, initTexture);
                    level[outerLoop, innerLoop].View = view;
                    level[outerLoop, innerLoop].Projection = projection;
                    initTilePos.X += tileSize;
                }
                initTilePos.X = position.X;
                initTilePos.Z += tileSize;
            }
        }

        public void SetUpCover()
        {
            coverList = new List<Cover>();

            coverList.Add(new Crate(new Vector3(135, 0, 5), 75, 15, 15));
            coverList.Add(new Crate(new Vector3(200, 0, -200), 15));
            coverList.Add(new Crate(new Vector3(100, 0, -300), 15));
            coverList.Add(new Crate(new Vector3(-200, 0, -75), 15, 15, 125));
            coverList.Add(new Crate(new Vector3(-200, 0, -255), 125, 15, 15));

            coverList[0].CoverPoints.Add(new Vector3(150, 0, 35));
            coverList[1].CoverPoints.Add(new Vector3(230, 0, -200));
            coverList[2].CoverPoints.Add(new Vector3(110, 0, -340));
            coverList[3].CoverPoints.Add(new Vector3(-230, 1, 35));
            coverList[3].CoverPoints.Add(new Vector3(-230, 1, -75));
            coverList[4].CoverPoints.Add(new Vector3(-90, 0, -280));
            coverList[4].CoverPoints.Add(new Vector3(-200, 0, -280));

            foreach (Cover currentCover in coverList)
            {
                currentCover.View = view;
                currentCover.Projection = projection;
                currentCover.InitCoverStatus();
            }
        }

        public void SetUpNavMesh()
        {
            navMesh = new NavMesh();

            Node node1 = new Node(new Vector3(-230, 1, 35));

            Node node2 = new Node(new Vector3(-230, 1, -75));

            Node node3 = new Node(new Vector3(110, 1, 105));
            Node node4 = new Node(new Vector3(-180, 1, 105));

            Node node5 = new Node(new Vector3(150, 0, 35));
            Node node6 = new Node(new Vector3(350, 0, 35));
            Node node7 = new Node(new Vector3(350, 0, -120));
            Node node8 = new Node(new Vector3(230, 0, -200));
            Node node9 = new Node(new Vector3(0, 0, -120));
            Node node10 = new Node(new Vector3(230, 0, -290));
            Node node11 = new Node(new Vector3(110, 0, -340));
            Node node12 = new Node(new Vector3(-90, 0, -280));
            Node node13 = new Node(new Vector3(-200, 0, -280));
            Node node14 = new Node(new Vector3(-200, 0, -350));
            Node node15 = new Node(new Vector3(-350, 0, -350));
            Node node16 = new Node(new Vector3(-350, 0, -120));
            Node node17 = new Node(new Vector3(150, 0, -120));
            Node node18 = new Node(new Vector3(150, 0, -230));
            Node node19 = new Node(new Vector3(-230, 1, 75));

            WeightedEdge edge1 = new WeightedEdge(node1, node2);
            WeightedEdge edge2 = new WeightedEdge(node3, node4);
            WeightedEdge edge3 = new WeightedEdge(node3, node5);
            WeightedEdge edge4 = new WeightedEdge(node5, node6);
            WeightedEdge edge5 = new WeightedEdge(node6, node7);
            WeightedEdge edge6 = new WeightedEdge(node7, node8);
            WeightedEdge edge7 = new WeightedEdge(node7, node17);
            WeightedEdge edge8 = new WeightedEdge(node8, node10);
            WeightedEdge edge9 = new WeightedEdge(node10, node11);
            WeightedEdge edge10 = new WeightedEdge(node11, node12);
            WeightedEdge edge11 = new WeightedEdge(node12, node13);
            WeightedEdge edge12 = new WeightedEdge(node13, node14);
            WeightedEdge edge13 = new WeightedEdge(node14, node15);
            WeightedEdge edge14 = new WeightedEdge(node15, node16);
            WeightedEdge edge15 = new WeightedEdge(node16, node2);
            WeightedEdge edge16 = new WeightedEdge(node17, node9);
            WeightedEdge edge17 = new WeightedEdge(node17, node18);
            WeightedEdge edge18 = new WeightedEdge(node18, node9);
            WeightedEdge edge19 = new WeightedEdge(node18, node10);
            WeightedEdge edge20 = new WeightedEdge(node18, node12);
            WeightedEdge edge21 = new WeightedEdge(node1, node19);
            WeightedEdge edge22 = new WeightedEdge(node4, node19);

            navMesh.addNode(node1);
            navMesh.addNode(node2);
            navMesh.addNode(node3);
            navMesh.addNode(node4);
            navMesh.addNode(node5);
            navMesh.addNode(node6);
            navMesh.addNode(node7);
            navMesh.addNode(node8);
            navMesh.addNode(node9);
            navMesh.addNode(node10);
            navMesh.addNode(node11);
            navMesh.addNode(node12);
            navMesh.addNode(node13);
            navMesh.addNode(node14);
            navMesh.addNode(node15);
            navMesh.addNode(node16);
            navMesh.addNode(node17);
            navMesh.addNode(node18);
            navMesh.addNode(node19);

            //Connections for the individual Nodes

            node1.addConnection(edge1);
            node1.addConnection(edge21);

            node2.addConnection(edge1);
            node2.addConnection(edge15);

            node3.addConnection(edge2);
            node3.addConnection(edge3);

            node4.addConnection(edge2);
            node4.addConnection(edge22);

            node5.addConnection(edge3);
            node5.addConnection(edge4);

            node6.addConnection(edge4);
            node6.addConnection(edge5);

            node7.addConnection(edge5);
            node7.addConnection(edge6);
            node7.addConnection(edge7);

            node8.addConnection(edge6);
            node8.addConnection(edge8);

            node9.addConnection(edge16);

            node10.addConnection(edge8);
            node10.addConnection(edge9);

            node11.addConnection(edge9);
            node11.addConnection(edge10);

            node12.addConnection(edge10);
            node12.addConnection(edge11);
            node12.addConnection(edge20);

            node13.addConnection(edge11);
            node13.addConnection(edge12);

            node14.addConnection(edge12);
            node14.addConnection(edge13);

            node15.addConnection(edge13);
            node15.addConnection(edge14);

            node16.addConnection(edge14);
            node16.addConnection(edge15);

            node17.addConnection(edge7);
            node17.addConnection(edge16);

            node18.addConnection(edge17);
            node18.addConnection(edge18);
            node18.addConnection(edge19);
            node18.addConnection(edge20);

            node19.addConnection(edge21);
            node19.addConnection(edge22);

            //Connections for the nav mesh

            navMesh.addConnection(edge1);
            navMesh.addConnection(edge2);
            navMesh.addConnection(edge3);
            navMesh.addConnection(edge4);
            navMesh.addConnection(edge5);
            navMesh.addConnection(edge6);
            navMesh.addConnection(edge7);
            navMesh.addConnection(edge8);
            navMesh.addConnection(edge9);
            navMesh.addConnection(edge10);
            navMesh.addConnection(edge11);
            navMesh.addConnection(edge12);
            navMesh.addConnection(edge13);
            navMesh.addConnection(edge14);
            navMesh.addConnection(edge15);
            navMesh.addConnection(edge16);
            navMesh.addConnection(edge17);
            navMesh.addConnection(edge18);
            navMesh.addConnection(edge19);
            navMesh.addConnection(edge20);
            navMesh.addConnection(edge21);
            navMesh.addConnection(edge22);
        }

        public Vector3[,] InitialSquadPositions
        {
            get { return initSquadPos; }
        }

        public NavMesh NavGraph
        {
            get { return navMesh; }
        }

        public void InvalidateCover()
        {
            foreach (Cover currentCover in coverList)
            {
                for (int index = 0; index < currentCover.CoverPoints.Count; index++)
                {
                    currentCover.ChangeCoverStatus(index, false);
                }
            }
        }

        public bool IsCoverAvailable()
        {
            foreach (Cover currentCover in coverList)
            {
                for (int index = 0; index < currentCover.CoverPoints.Count; index++)
                {
                    if (!currentCover.CheckIfCoverPointOccupied(index))
                    {
                        return true;
                    }
                }
            }
            return false;
        }

        public bool IsActorInCover(Vector3 actorPosition)
        {
            for (int outer = 0; outer < coverList.Count; outer++)
            {
                for (int inner = 0; inner < coverList[outer].CoverPoints.Count; inner++)
                {
                    Vector3 currentCoverPoint = coverList[outer].CoverPoints[inner];

                    if ((currentCoverPoint.X > actorPosition.X - 20) && (currentCoverPoint.X < actorPosition.X + 20) &&
                       (currentCoverPoint.Z > actorPosition.Z - 20) && (currentCoverPoint.Z < actorPosition.Z + 20)
                        )
                    {
                        //Update current cover point status and return true
                        coverList[outer].ChangeCoverStatus(inner, true);

                        return true;
                    }
                }
            }
            return false;
        }

        public void MakeCoverAvailable(Vector3 target)
        {
            for (int outer = 0; outer < coverList.Count; outer++)
            {
                for (int inner = 0; inner < coverList[outer].CoverPoints.Count; inner++)
                {
                    Vector3 currentCoverPoint = coverList[outer].CoverPoints[inner];

                    if ((currentCoverPoint.X > target.X - 20) && (currentCoverPoint.X < target.X + 20) &&
                       (currentCoverPoint.Z > target.Z - 20) && (currentCoverPoint.Z < target.Z + 20)
                        )
                    {
                        //Update current cover point status and return true
                        coverList[outer].ChangeCoverStatus(inner, false);
                    }
                }
            }
        }

        public Vector3? FindNearestCover(Vector3 actorPosition)
        {
            int[] nearestCoverIndex = new int[2]; ;
            Vector3? nearestCover = null;
            float smallestDistance = 99999;
            float targetDistance;

            for (int outer = 0; outer < coverList.Count; outer++)
            {
                for (int inner = 0; inner < coverList[outer].CoverPoints.Count; inner++)
                {
                    Vector3 currentCoverPoint = coverList[outer].CoverPoints[inner];
                    targetDistance = Math.Abs((actorPosition - currentCoverPoint).Length());
                    if ( (targetDistance < smallestDistance) && 
                        (!coverList[outer].CheckIfCoverPointOccupied(inner)) 
                        )
                    {
                        nearestCover = currentCoverPoint;
                        smallestDistance = targetDistance;

                        nearestCoverIndex[0] = outer;
                        nearestCoverIndex[1] = inner;
                    }
                }
            }

            if (nearestCover != null)
            {
                coverList[nearestCoverIndex[0]].ChangeCoverStatus(nearestCoverIndex[1], true);
            }

            return nearestCover;
        }

        public Vector3? FindOptimalCover(Vector3 targetPosition, float optimal)
        {
            int[] optimalCoverIndex = new int[2];
            Vector3? optimalCover = null;
            float largestDistance = 0;
            float targetDistance;

            while (optimal < 400)
            {
                for (int outer = 0; outer < coverList.Count; outer++)
                {
                    for (int inner = 0; inner < coverList[outer].CoverPoints.Count; inner++)
                    {
                        Vector3 currentCoverPoint = coverList[outer].CoverPoints[inner];
                        targetDistance = Math.Abs((targetPosition - currentCoverPoint).Length());
                        if (
                            (targetDistance < optimal) &&
                            (targetDistance > largestDistance) &&
                            (!coverList[outer].CheckIfCoverPointOccupied(inner) )
                            )
                        {
                            optimalCover = currentCoverPoint;
                            largestDistance = targetDistance;

                            optimalCoverIndex[0] = outer;
                            optimalCoverIndex[1] = inner;
                        }
                    }
                }

                //Increment optimal range

                optimal += 20f;
            }

            if (optimalCover != null)
            {
                coverList[optimalCoverIndex[0]].ChangeCoverStatus(optimalCoverIndex[1], true);
            }

            return optimalCover;
        }

        public Vector3 FindSafeSpot(Vector3 actorPosition, Vector3 target, int safeRange)
        {
            return navMesh.FindSafeNode(actorPosition, target, safeRange);
        }

        public void WallObstacleAvoidance(SquadActor actor)
        {
            Vector3 separateAdjust;
            
            if (actor.getPosition().Z < -(levelSize * tileSize)/2)
            {
                separateAdjust = actor.MaxAcceleration * (new Vector3(0, 0, 1));
                actor.setVelocity(actor.getVelocity() + (separateAdjust - actor.getVelocity()));
            }
            
            if (actor.getPosition().Z > (levelSize * tileSize) / 2)
            {
                separateAdjust = actor.MaxAcceleration * (new Vector3(0, 0, -1));
                actor.setVelocity(actor.getVelocity() + (separateAdjust - actor.getVelocity()));
            }
            
            if (actor.getPosition().X < -(levelSize * tileSize) / 2)
            {
                separateAdjust = actor.MaxAcceleration * (new Vector3(1, 0, 0));
                actor.setVelocity(actor.getVelocity() + (separateAdjust - actor.getVelocity()));
            }

            if (actor.getPosition().X > (levelSize * tileSize) / 2)
            {
                separateAdjust = actor.MaxAcceleration * (new Vector3(-1, 0, 0));
                actor.setVelocity(actor.getVelocity() + (separateAdjust - actor.getVelocity()));
            }

            

            foreach (Cover currentCover in coverList)
            {
                Crate tempCrate = (Crate)currentCover;


                if  (
                    ( (tempCrate.Width == 0) && (Math.Abs((currentCover.getOrigin() - actor.getOrigin()).Length()) < 10) ) ||
                     ( (tempCrate.Width != 0) && (Math.Abs(currentCover.getOrigin().X - actor.getOrigin().X) < tempCrate.Width/2)
                     && (Math.Abs(currentCover.getOrigin().Z - actor.getOrigin().Z) < tempCrate.Depth / 2)
                     )
                    )
                {
                    separateAdjust = currentCover.getPosition() - actor.getOrigin();
                    separateAdjust.Normalize();
                    separateAdjust *= -actor.MaxAcceleration;
                    separateAdjust.Y = 0;

                    actor.setVelocity(actor.getVelocity() + (separateAdjust - actor.getVelocity()));

                }
            }
             
        }

        /// <summary>
        /// Draws the level tiles that make up the level
        /// </summary>

        public void Draw()
        {
            foreach (LevelTile tileRow in level)
            {
                tileRow.Draw();
            }

            foreach (Cover currentCover in coverList)
            {
                currentCover.VRender();
            }

            navMesh.Draw();
        }


        // Data Members

        //Varibles for creating the CameraLookAt Matrix

        Vector3 cameraPos;
        Vector3 cameraTarget;
        Vector3 lookUpVector;

        //Matrices for view and projection

        Matrix view;
        Matrix projection;

        //Level parameters

        List<Cover> coverList;

        Vector3 position;
        float tileSize;
        int levelSize; //Note: Measured in LevelTiles
        LevelTile[,] level;
        Vector3[,] initSquadPos;

        NavMesh navMesh;

        //Tile layout array. Determines the layout of the level
        // 0 = Grass
        // 1 = Water

        int[,] tileTypeLayout = {
                                   {0,0,0,0,0,0,0,0,0,0},
                                   {0,1,0,0,0,0,0,0,0,0},
                                   {0,1,1,1,0,0,0,0,0,0},
                                   {0,0,0,1,0,0,0,0,0,0},
                                   {0,0,0,1,0,1,1,1,1,0},
                                   {0,0,0,1,1,1,0,0,0,0},
                                   {0,0,0,0,0,0,0,0,0,0},
                                   {0,0,0,0,0,0,0,0,0,0},
                                   {0,0,0,0,0,0,0,0,0,0},
                                   {0,0,0,0,0,0,0,0,0,0},
                               };
    }
}
